﻿using System;
using System.Collections.Generic;
using System.Text;
using Sinacor.Infra.Core.ProcessManager.Server;
using Sinacor.Infra.Service.Security.Authorization;
using PSE.ProcessManager.Server.Process.Security;
using Sinacor.Infra.Core.ProcessManager.Server.Security;
using PSE.Framework.Communication.Client.File.FlatFile;
using PSE.Framework.Communication.Adapters.File;
using System.Threading;
using Sinacor.Infra.Common.Exceptions;
using Sinacor.Infra.Service.ExceptionHandling;
using Sinacor.Infra.Service.Validation;
using PSE.Framework.Communication.Client.File.XmlFile;
using PSE.Framework.Communication.Client.File;

namespace Sinacor.Process.Bovespa.Negocio.Server.ProcessLogic
{
    public class Importacao : BaseProcessLogic
    {
        #region Atributos

        private ManualResetEvent _importFinishControl;
        private AsyncFileInputClient<object> _client = null;
        private List<ProcessEntity.NegocioDetail> _negocios = null;
        private ProcessEntity.NegocioHeader _header = null;
        private ProcessEntity.NegocioTrailer _trailer = null;
        private int _erroCodigoCorretoraDivergente = 109754;
        private int _erroDataPregaoDiferenteMovimento = 109755;
        private int _erroArquivoInvalido = 109797;
        private int _erroMovimentoFechado = 109756;
        private int _erroQuantidadeRegistrosInvalido = 109800;
        private int _erroGeralImportacaoArquivo = 109821;
        private Translator.LayoutToProcessEntity.Negocio _translatorNegocio;
        private bool _erroLeituraArquivo = false;

        #endregion

        #region Eventos

        public event EventHandler ImportacaoIniciada;
        public event EventHandler ProcessamentoImportacaoIniciado;
        public event EventHandler ImportacaoConcluida;

        #endregion

        #region Construtor

        /// <summary>
        /// Construtor padrão
        /// </summary>
        public Importacao()
        {
            _translatorNegocio = new Translator.LayoutToProcessEntity.Negocio();
        }

        #endregion

        #region Propriedades

        public int ErroGeralImportacaoArquivo
        {
            get { return _erroGeralImportacaoArquivo; }
            set { _erroGeralImportacaoArquivo = value; }
        }

        public int ErroCodigoCorretoraDivergente
        {
            get { return _erroCodigoCorretoraDivergente; }
            set { _erroCodigoCorretoraDivergente = value; }
        }

        public int ErroDataPregaoDiferenteMovimento
        {
            get { return _erroDataPregaoDiferenteMovimento; }
            set { _erroDataPregaoDiferenteMovimento = value; }
        }

        public int ErroMovimentoFechado
        {
            get { return _erroMovimentoFechado; }
            set { _erroMovimentoFechado = value; }
        }

        public int ErroArquivoInvalido
        {
            get { return _erroArquivoInvalido; }
            set { _erroArquivoInvalido = value; }
        }

        public int ErroQuantidadeRegistrosInvalido
        {
            get { return _erroQuantidadeRegistrosInvalido; }
            set { _erroQuantidadeRegistrosInvalido = value; }
        }

        public bool ErroLeituraArquivo
        {
            get { return _erroLeituraArquivo; }
            set { _erroLeituraArquivo = value; }
        }

        #endregion

        #region EventHandler

        private void _client_RecordReadError(object sender, RecordReadErrorEventArgs e)
        {
            TratarErroLeituraArquivo(e.Exception);
        }

        private void _client_RecordRead(object sender, RecordEventArgs e)
        {
            Layout.NegocioXMLIdentificador xmlNEGS;

            try
            {
                if (e.Record is Layout.NegocioHeader)
                {
                    _header = _translatorNegocio.ToNegocioHeader((Layout.NegocioHeader)e.Record);
                }
                else if (e.Record is Layout.NegocioDetail)
                {
                    if (e.Record is Layout.NegocioRDetail)
                    {
                        _negocios.Add(_translatorNegocio.ToNegocioRDetail((Layout.NegocioRDetail)e.Record));
                    }
                    else
                    {
                        _negocios.Add(_translatorNegocio.ToNegocioSDetail((Layout.NegocioSDetail)e.Record));
                    }
                }
                else if (e.Record is Layout.NegocioTrailer)
                {
                    _trailer = _translatorNegocio.ToNegocioTrailer((Layout.NegocioTrailer)e.Record);
                }
                else if (e.Record is Layout.NegocioXMLIdentificador)
                {
                    xmlNEGS = (Layout.NegocioXMLIdentificador)e.Record;

                    try
                    {
                        _header = _translatorNegocio.ToNegocioHeader(xmlNEGS);

                        if (xmlNEGS.Evento != null
                            && xmlNEGS.Evento.Negocio != null)
                            _negocios.AddRange(_translatorNegocio.ToNegocioSDetail(xmlNEGS.Evento.Negocio).ToArray());

                        _trailer = _translatorNegocio.ToNegocioTrailer(xmlNEGS);
                    }
                    catch (Exception ex)
                    {
                        TratarErroLeituraArquivo(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                this.HandleException(ex, true);

                FecharClient();
            }
        }

        private void _client_FileClosed(object sender, FileClosedEventArgs e)
        {
            FecharClient();
        }

        private void _client_FileCanceled(object sender, FileCanceledEventArgs e)
        {
            FecharClient();
        }

        private void _client_FileOpened(object sender, FileOpenedEventArgs e)
        {
            if (!IsAborted)
            {
                _header = null;
                _trailer = null;

                if (_negocios == null)
                    _negocios = new List<ProcessEntity.NegocioDetail>();

                _negocios.Clear();
            }
        }

        #endregion

        #region Métodos Públicos

        public void ImportarNegocios(ProcessEntity.Parameter.FiltroImportacaoNegocio entity)
        {
            ResourceAccess.ImportacaoExportacao raImportacaoExportacao;
            ResourceAccess.Importacao raImportacao;
            int codigoEmpresaBolsa;
            Communication.ControleMovimentoOrdens communicatonControleMovimentoOrdens;
            Communication.ControleMovimentoBolsa communicatonControleMovimentoBolsa;
            ProcessEntity.InformacoesMovimento informacaoMovimento;
            SinacorValidation validation = null;
            decimal porcentagem = 5;
            decimal porcentagemItem;
            string dataPregaoFormatada;
            int atualizarStatus;
            ProcessEntity.Parameter.FiltroProcessaNegocio filtroProc;

            try
            {
                if (ImportacaoIniciada != null)
                {
                    ImportacaoIniciada(this, new EventArgs());
                }

                if (!IsAborted)
                {
                    validation = new SinacorValidation();
                    validation.Validate<ProcessEntity.Parameter.FiltroImportacaoNegocio>(entity);

                    AtualizaStatus(porcentagem, "Bovespa.ImportacaoExportacao.Label.LendoArquivo");
                    LerArquivoNegocio(entity);

                    if (!IsAborted
                        && !_erroLeituraArquivo)
                    {
                        porcentagem = 10;
                        AtualizaStatus(porcentagem, "Bovespa.ImportacaoExportacao.Label.ValidandoArquivo");

                        if (entity.TipoFormatoArquivo == ProcessEntity.Enum.TipoFormatoArquivo.TXT
                            && _trailer.TotalRegistrosGerados != _negocios.Count + 2)
                            throw new BusinessException(ErroQuantidadeRegistrosInvalido);

                        raImportacaoExportacao = new ResourceAccess.ImportacaoExportacao();

                        if (entity.TipoSistema == ProcessEntity.Enum.TipoSistema.Bolsa)
                        {
                            communicatonControleMovimentoBolsa = new Communication.ControleMovimentoBolsa();
                            informacaoMovimento = communicatonControleMovimentoBolsa.ObterInformacoesMovimento();
                        }
                        else
                        {
                            communicatonControleMovimentoOrdens = new Communication.ControleMovimentoOrdens();
                            informacaoMovimento = communicatonControleMovimentoOrdens.ObterInformacoesMovimento();
                        }

                        if (informacaoMovimento.EstadoMovimento == ProcessEntity.Enum.TipoMovimento.Fechado)
                            throw new BusinessException(ErroMovimentoFechado);

                        if (entity.TipoArquivo == ProcessEntity.Enum.TipoArquivo.NEGR)
                        {
                            codigoEmpresaBolsa = raImportacaoExportacao.ObterCodigoEmpresaBolsa();
                        }
                        else
                        {
                            codigoEmpresaBolsa = raImportacaoExportacao.ObterCodigoEmpresaBolsaCorrespondente(new ProcessEntity.Parameter.FiltroImportacaoNegocio
                            {
                                CodigoCorrespondente = entity.CodigoCorrespondente
                            });
                        }

                        if (_header.CodigoUsuario != codigoEmpresaBolsa)
                            throw new BusinessException(ErroCodigoCorretoraDivergente);

                        if (_header.DataPregao != informacaoMovimento.DataMovimento)
                            throw new BusinessException(ErroDataPregaoDiferenteMovimento);

                        if (!IsAborted)
                        {
                            raImportacao = new ResourceAccess.Importacao();
                            raImportacao.LimparTabelaImportacao();

                            if (!IsAborted)
                            {
                                porcentagemItem = (_negocios.Count > 0) ? 70M / Convert.ToDecimal(_negocios.Count) : 70;

                                dataPregaoFormatada = _header.DataPregao.ToString("yyyyMMdd");

                                atualizarStatus = (_negocios.Count < 100) ? 10 : 100;

                                BeginTransaction(AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData.CompanyId);

                                for (int i = 0; i < _negocios.Count; i++)
                                {
                                    if (IsAborted)
                                        break;

                                    _negocios[i].DataPregao = dataPregaoFormatada;
                                    _negocios[i].PrecoNegocio *= 100;
                                    _negocios[i].PrecoExercicioSerie *= 100;
                                    porcentagem += porcentagemItem;
                                    raImportacao.ImportarNegocio(_negocios[i]);

                                    if (i % atualizarStatus == 0)
                                    {
                                        AtualizaStatus(porcentagem, "Bovespa.ImportacaoExportacao.Label.ImportandoNegocios");
                                    }

                                    if (i % 100 == 0)
                                    {
                                        CommitTransaction();
                                        BeginTransaction(AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData.CompanyId);
                                    }
                                }

                                if (!IsAborted)
                                {
                                    CommitTransaction();

                                    porcentagem = 80;
                                    AtualizaStatus(porcentagem, "Bovespa.ImportacaoExportacao.Label.ProcessandoNegocios");

                                    if (!IsAborted)
                                    {
                                        filtroProc = new ProcessEntity.Parameter.FiltroProcessaNegocio();
                                        filtroProc.CodigoBolsa = codigoEmpresaBolsa;

                                        if (entity.TipoArquivo == ProcessEntity.Enum.TipoArquivo.NEGR)
                                        {
                                            filtroProc.CodigoCorrespondente = 0;
                                        }
                                        else
                                        {
                                            filtroProc.CodigoCorrespondente = (entity.CodigoCorrespondente.HasValue)
                                                                                ? entity.CodigoCorrespondente.Value
                                                                                : AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData.CompanyId;
                                        }

                                        ProcessarNegocios(filtroProc);

                                        porcentagem = 90;
                                        AtualizaStatus(porcentagem, "Bovespa.ImportacaoExportacao.Label.FinalizandoProcessamento");
                                    }
                                }
                            }
                        }
                    }
                }

                if (!_erroLeituraArquivo)
                {
                    if (!IsAborted)
                    {
                        CommitTransaction();

                        if (ImportacaoConcluida != null)
                        {
                            ImportacaoConcluida(this, new EventArgs());
                        }
                    }
                    else
                    {
                        RollbackTransaction();
                    }
                }
            }
            catch
            {
                RollbackTransaction();

                throw;
            }
            finally
            {
                FecharClient();
            }
        }

        #endregion

        #region Métodos Privados

        private void ProcessarNegocios(ProcessEntity.Parameter.FiltroProcessaNegocio entity)
        {
            ProcessEntity.Resultado resultado;

            if (ProcessamentoImportacaoIniciado != null)
            {
                ProcessamentoImportacaoIniciado(this, new EventArgs());
            }

            resultado = new ResourceAccess.Importacao().ProcessarNegocios(entity);

            if (!resultado.Sucesso)
                TratarExcecoes(resultado);
        }

        private void TratarExcecoes(ProcessEntity.Resultado request)
        {
            List<Object> _params;

            if (request.CodigoErro == -1)
            {
                throw new BusinessException(this.ErroGeralImportacaoArquivo);
            }
            else
            {
                if (!String.IsNullOrEmpty(request.Argumentos))
                {
                    _params = ErrorMessage.ConvertXmlArgumentsToParameters(request.Argumentos);

                    throw new BusinessException(request.CodigoErro, _params.ToArray());
                }
                else
                {
                    throw new BusinessException(request.CodigoErro);
                }
            }
        }

        private void LerArquivoNegocio(ProcessEntity.Parameter.FiltroImportacaoNegocio entity)
        {
            _importFinishControl = new ManualResetEvent(false);

            if (entity.TipoFormatoArquivo == ProcessEntity.Enum.TipoFormatoArquivo.TXT)
            {
                _client = new FlatFileInputClient();

                if (entity.TipoArquivo == ProcessEntity.Enum.TipoArquivo.NEGR)
                {
                    _client.Open("ImportacaoNegR");
                }
                else
                {
                    _client.Open("ImportacaoNegS");
                }
            }
            else
            {
                _client = new XmlFileInputClient();
                _client.Open("ImportacaoNegSXML");
            }

            _client.FileOpened += new EventHandler<FileOpenedEventArgs>(_client_FileOpened);
            _client.FileClosed += new EventHandler<FileClosedEventArgs>(_client_FileClosed);
            _client.RecordRead += new EventHandler<RecordEventArgs>(_client_RecordRead);
            _client.RecordReadError += new EventHandler<RecordReadErrorEventArgs>(_client_RecordReadError);
            _client.FileCanceled += new EventHandler<FileCanceledEventArgs>(_client_FileCanceled);

            _client.BeginImport(new FileReceiveParameter(System.IO.Path.GetFileName(entity.NomeArquivo)));

            _importFinishControl.WaitOne();
        }

        private void TratarErroLeituraArquivo(Exception exception)
        {
            _erroLeituraArquivo = true;

            this.HandleException(new BusinessException(ErroArquivoInvalido, exception), true);

            FecharClient();
        }

        private void FecharClient()
        {
            if (_client != null)
            {
                if (_client.Status == PSE.Framework.Communication.CommunicationStatus.Opened)
                {
                    _client.EndImport();
                    _client.Close();
                }

                _client = null;
            }

            if (_importFinishControl != null)
                _importFinishControl.Set();
        }

        private void AtualizaStatus(decimal progress, string resourceKey)
        {
            this.Status.Progress = Convert.ToInt32(Math.Round(progress, 0, MidpointRounding.ToEven));
            this.Status.Step = resourceKey;
            this.SendStatus();
        }

        #endregion
    }
}
